package dbUtils

import (
	"encoding/json"
	"fmt"
	"gitee.com/lv_baobao/gcore/customTypes/dateTime"
	"gitee.com/lv_baobao/gcore/goroutingHelper"
	"gitee.com/lv_baobao/gcore/helper"
	"gitee.com/lv_baobao/gcore/logHelper"
	"gitee.com/lv_baobao/gcore/orm"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"
)

type DbLogType int

const (
	Insert     DbLogType = 0
	Update     DbLogType = 1
	Delete     DbLogType = 2
	ExecuteSql DbLogType = 3
)

type DbLogContent struct {
	DataBase     string
	DataBaseKey  string
	TableName    string
	Fields       []string
	RecordValues []interface{}

	WhereFields      []string
	WhereFieldValues []interface{}

	HasAutoIncr bool
	HasPkField  bool
	Sql         string
	LogType     DbLogType
	LogTime     dateTime.DateTime
}

type DbLogHelper struct {
	FileNameTemp string
	FileName     string

	FileIndex       int
	MaxFileSize     int64
	MaxWriteSeconds float64
	FileLock        sync.Mutex
}

var dbLogOnce sync.Once
var dbLog *DbLogHelper

func NewDbLogHelper() *DbLogHelper {
	dbLogOnce.Do(func() {
		dbLog = &DbLogHelper{}
		dbLog.FileNameTemp = "dblog/logs/log"
		dbLog.FileName = "dblog/logs/log_0"
		//dbLog.FileIndex=0
		dbLog.MaxFileSize = 1024 * 50
		dbLog.MaxWriteSeconds = 10
		dbLog.FileIndex = dbLog.GetLatestFileIndex()
		dbLog.FileName = fmt.Sprintf("%s_%d", dbLog.FileNameTemp, dbLog.FileIndex)
		os.MkdirAll("dblog/logs", 0766)
		os.MkdirAll("dblog/senderLogs", 0766)
		dbLog.startMoveFile()
	})
	return dbLog
}

func (d *DbLogHelper) WriteInsertedLog(log interface{}, tableName string, dataBase string, dataBaseKey string) error {
	newSchema := orm.NewSchema(log)
	insertRecordValues := newSchema.GetRecordValues(nil)
	var pkField = newSchema.PkField
	if tableName == "" {
		tableName = newSchema.Name
	}
	if dataBaseKey == "" {
		dataBaseKey = "writedb"
	}
	var logContent = &DbLogContent{
		DataBase:     dataBase,
		TableName:    tableName,
		Fields:       newSchema.FieldNames,
		RecordValues: insertRecordValues,
		LogType:      Insert,
		LogTime:      dateTime.Now(),
		HasPkField:   pkField != nil,
		HasAutoIncr:  pkField != nil && pkField.AutoIncr,
		DataBaseKey:  dataBaseKey,
	}
	return d.WriteDbLog(logContent)
}

func (d *DbLogHelper) WriteUpdatedLogByEntity(log interface{}, tableName string, dataBase string, dataBaseKey string) error {
	newSchema := orm.NewSchema(log)
	updateRecordValues := newSchema.GetUpdateRecordValues(nil)
	updateFieldNames := newSchema.GetUpdateFieldNames()
	var destValue = reflect.Indirect(reflect.ValueOf(log))
	pkValue := newSchema.GetFieldValue(destValue, *newSchema.PkField)
	//var pkField = newSchema.PkField
	if tableName == "" {
		tableName = newSchema.Name
	}
	return d.WriteUpdatedLog(tableName, updateFieldNames, updateRecordValues, []string{fmt.Sprintf(" and %s=?", newSchema.PkField.Name)}, []interface{}{pkValue}, dataBase, dataBaseKey)
}

func (d *DbLogHelper) WriteUpdatedLog(table string, fields []string, fieldValues []interface{}, whereFields []string, whereFieldsValue []interface{}, dataBase string, dataBaseKey string) error {
	var logContent = &DbLogContent{
		DataBase:         dataBase,
		TableName:        table,
		Fields:           fields,
		RecordValues:     fieldValues,
		WhereFields:      whereFields,
		WhereFieldValues: whereFieldsValue,
		LogType:          Update,
		LogTime:          dateTime.Now(),
		DataBaseKey:      dataBaseKey,
	}
	return d.WriteDbLog(logContent)
}

func (d *DbLogHelper) WriteDeletedLogByEntity(log interface{}, dataBase string, dataBaseKey string) error {
	newSchema := orm.NewSchema(log)
	var destValue = reflect.Indirect(reflect.ValueOf(log))
	pkValue := newSchema.GetFieldValue(destValue, *newSchema.PkField)
	return d.WriteDeletedLog(newSchema.Name, []string{fmt.Sprintf(" and %s=?", newSchema.PkField.Name)}, []interface{}{pkValue}, dataBase, dataBaseKey)
}

func (d *DbLogHelper) WriteDeletedLog(table string, whereFields []string, whereFieldsValue []interface{}, dataBase string, dataBaseKey string) error {
	var logContent = &DbLogContent{
		DataBase:         dataBase,
		TableName:        table,
		WhereFields:      whereFields,
		WhereFieldValues: whereFieldsValue,
		LogType:          Delete,
		LogTime:          dateTime.Now(),
		DataBaseKey:      dataBaseKey,
	}
	return d.WriteDbLog(logContent)
}

func (d *DbLogHelper) WriteExecuteSqlLog(sql string, args []interface{}, dataBase string, dataBaseKey string) error {
	var logContent = &DbLogContent{
		DataBase:     dataBase,
		TableName:    "",
		LogType:      ExecuteSql,
		LogTime:      dateTime.Now(),
		Sql:          sql,
		RecordValues: args,
		DataBaseKey:  dataBaseKey,
	}
	return d.WriteDbLog(logContent)
}

func (d *DbLogHelper) WriteDbLog(logContent *DbLogContent) error {
	goroutingHelper.Start(func() {
		NewDbSyncClient().ReplayDbLog(logContent)
	})
	return nil

	bytes, err := json.Marshal(logContent)
	if err != nil {
		return err
	}
	d.FileLock.Lock()
	defer d.FileLock.Unlock()
	fl, err := os.OpenFile(d.FileName, os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer fl.Close()
	_, err = fl.Write(bytes)
	fl.WriteString("\n")
	d.CheckNewFile(fl)
	return err
}

func (d *DbLogHelper) CheckNewFile(fl *os.File) bool {
	stat, _ := fl.Stat()
	size := stat.Size()
	seconds := time.Now().Sub(stat.ModTime()).Seconds()
	filePath := fl.Name()
	index := strings.LastIndex(filePath, "/")
	fileName := filePath[index+1 : len(filePath)]
	if size > d.MaxFileSize || seconds > d.MaxWriteSeconds {
		//d.FileLock.Lock()
		//defer d.FileLock.Unlock()
		d.FileIndex++
		fl.Close()
		err := os.Rename(filePath, fmt.Sprintf("dblog/senderLogs/%d_%s", time.Now().Unix(), fileName))
		if err != nil {
			logHelper.Error(err)
		}
		d.FileName = fmt.Sprintf("%s_%d", d.FileNameTemp, d.FileIndex)
		return true
	}
	return false
}

func (d *DbLogHelper) GetLatestFileIndex() int {
	files, err := ioutil.ReadDir("dblog/logs")
	if err != nil {
		return 0
	}
	var maxIndex = 0
	for _, file := range files {
		index := strings.LastIndex(file.Name(), "_")
		if index > -1 {
			s := file.Name()[index+1 : len(file.Name())]
			fileIndex, _ := strconv.Atoi(s)
			if fileIndex > maxIndex {
				maxIndex = fileIndex
			}
		}
	}
	fmt.Println("maxFileIndex", maxIndex)
	return maxIndex
}

func (d *DbLogHelper) startMoveFile() {
	helper.NewScheduleHelper().Start("迁移文件", time.Second*1, func() {
		path, err := d.GetOldFirstFilePath()
		if err != nil {
			return
		}
		fl, err := os.OpenFile(path, os.O_RDONLY, 0644)
		if err != nil {
			return
		}
		defer fl.Close()

		stat, err := fl.Stat()
		if err != nil {
			return
		}
		if time.Now().Sub(stat.ModTime()).Seconds() > (3) {
			fmt.Println("find file", path)
			index := strings.LastIndex(path, "/")
			fileName := path[index+1 : len(path)]
			fl.Close()
			err = os.Rename(path, fmt.Sprintf("dblog/senderLogs/%d_%s", time.Now().Unix(), fileName))
			if err != nil {
				logHelper.Error(err)
			}
		}
	})
}

func (d *DbLogHelper) GetOldFirstFilePath() (string, error) {
	files, err := ioutil.ReadDir("dblog/logs")
	if err != nil {
		return "", err
	}
	if len(files) == 0 {
		return "", nil
	}

	var tempFile = files[0]
	for _, file := range files {
		if file.ModTime().Before(tempFile.ModTime()) {
			tempFile = file
		}
	}
	filePath := fmt.Sprintf("dblog/logs/%s", tempFile.Name())
	return filePath, nil
}
